<html>
<!-- Head tag -->
<head><meta name="generator" content="Hexo 3.9.0">

    <meta name="google-site-verification" content="uxeL3ivCjEkmCPEWS1owNMkK9VHPxOMCjcaMHaQ38Bo">
    <meta name="google-site-verification" content="yU7d61qsV4eAvSOazt85VJMYfiEDjZjcaXwyQKGP5Bc">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
	
	
		<link href="/favicon.ico" rel="icon">
	 
      <title>Inoki in KDE</title>
	<link rel="stylesheet" href="/css/font-awesome/css/font-awesome.css">
	<link rel="stylesheet" href="/css/style.css">
	<link rel="stylesheet" href="/css/highlight.css">
	
	
<!-- Google Analytics -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-108089983-2"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-108089983-2');
</script>

<!-- End Google Analytics -->

<!-- Google Adsense -->
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<script>
  (adsbygoogle = window.adsbygoogle || []).push({
    google_ad_client: "ca-pub-2713518338457470",
    enable_page_level_ads: true
  });
</script>


</head>
<body>
	<div id="site" class="site">
		<div id="sidebar" class="sidebar">
			<header id="header" class="site-header">
	<div class="site-branding">
		<h1 class="site-title">
			
				<a href="/images/avatar-small.png" class="avatar-circle"><img src="/images/avatar-small.png" /></a>
			
			<a href="/" rel="home">Inoki ❤️ KDE</a></h1>
		<p class="site-description"></p>
		<button class="secondary-toggle font-asesome-icon">Menu and widgets</button>
	</div>
</header>
<div id="secondary" class="secondary">
	<nav class="main-navigation">
                         <ul id="menu-demo-menu" class="nav-menu">
						 
							<li class="menu-item"><a href="/">Home</a></li>
						
							<li class="menu-item"><a href="/archives">Archives</a></li>
						
							<li class="menu-item"><a href="/about">AboutMe</a></li>
						
                         </ul>
    </nav>
	
		<aside class="widget social-navigation">
    <ul>
        <li>
            <a href="http://twitter.com/IIInoki">
                <i class="fa fa-twitter" aria-hidden="true"></i>
            </a>
        </li>
        <li>
            <a href="https://www.facebook.com/noki.noki.10">
                <i class="fa fa-facebook-square" aria-hidden="true"></i>
            </a>
        </li>
        <li>
            <a href="https://github.com/inokinoki">
                <i class="fa fa-github" aria-hidden="true"></i>
            </a>
        </li>
        <li>
            <a href="https://linkedin.com/in/weixuan-xiao-032725108/">
                <i class="fa fa-linkedin" aria-hidden="true"></i>
            </a>
        </li>
    </ul>
</aside>
	
		
<aside class="widget">
		<h3 class="widget-title">Recent Posts</h3>		
		<ul>
			
          <li>
            <a href="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/">KDE Connect macOS Release</a>
          </li>
        
          <li>
            <a href="/2019/07/24/DBus/">DBus connection on macOS</a>
          </li>
        
          <li>
            <a href="/2019/07/18/KDE-Connect-macOS-plugin-notification/">Enable notification plugin in KDE Connect on macOS</a>
          </li>
        
          <li>
            <a href="/2019/07/16/KDE-Connect-macOS/">Connect your Android phone with your Mac via KDE Connect</a>
          </li>
        
          <li>
            <a href="/2019/05/26/Craft-packager/">KDE Craft Packager on macOS</a>
          </li>
        
		</ul>
	</aside>


	
		
  <aside class="widget">
		<h3 class="widget-title">Tag Cloud</h3>
        <a href="/tags/Craft/" style="font-size: 13.33px;">Craft</a> <a href="/tags/DBus/" style="font-size: 10px;">DBus</a> <a href="/tags/GSoC/" style="font-size: 16.67px;">GSoC</a> <a href="/tags/Inoki/" style="font-size: 10px;">Inoki</a> <a href="/tags/KDE-Connect/" style="font-size: 16.67px;">KDE Connect</a> <a href="/tags/macOS/" style="font-size: 20px;">macOS</a>
    </aside>

	
</div>

		</div>
		<div id="content" class="site-content">
			<main id="main" class="site-main" role="main">
				
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/" rel="bookmark">KDE Connect macOS Release</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<p>Now it’s the end of Google Summer of Code 2019. As my GSoC project, the port of KDE Connect on macOS has made great progress. You can find and download it in my <a href="https://github.com/Inokinoki/kde-blog/releases/tag/20190901" target="_blank" rel="noopener">blog release page</a>.</p>
<p><strong>Note:</strong> This post aims at presenting the features of KDE Connect which have been implemented on macOS. If you’d like to know more information, such as compilation of your own KDE Connect binary on macOS, please turn to another post in my post <a href="https://kde.inoki.cc/2019/07/16/KDE-Connect-macOS/">Connect your Android phone with your Mac via KDE Connect</a>. And if you’re interested in what I’ve done during Google Summer of Code, my status report of Google Summer of Code is <a href="https://community.kde.org/GSoC/2019/StatusReports/WeixuanXiao" target="_blank" rel="noopener">HERE</a>.</p>
<h1 id="Features"><a href="#Features" class="headerlink" title="Features"></a>Features</h1><p>In this chapter, I’d like to give you a preview of all features, as well as how to configure to make some of functions work.</p>
<h2 id="Launch-KDE-Connect"><a href="#Launch-KDE-Connect" class="headerlink" title="Launch KDE Connect"></a>Launch KDE Connect</h2><p>First, we can click on KDE Connect application - the <code>kdeconnect-indicator.app</code> to open it.</p>
<img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-Launch.gif" title="KDE Connect Launching">
<p>Then, we can open KDE Connect configuration window from the indicator in the tray bar of macOS. </p>
<img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/Kdeconnect-configuration.png" title="KDE Connect Configuration Window">
<p>As you can see, this is the main page of KDE Connect. All available plugins are here, you can enable/disable or configure them. In addition, available devices will be listed on the left, you can choose them to pair/unpair with them/it.</p>
<h2 id="Functions"><a href="#Functions" class="headerlink" title="Functions"></a>Functions</h2><h3 id="Pair-notification"><a href="#Pair-notification" class="headerlink" title="Pair notification"></a>Pair notification</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-PairNotification.gif" title="KDE Connect Pair notification">
<p>When you pair from your Andoid Phone, you should be able to receive a notification that shows the pair request. You can accept or reject it in the KDE Connect configuration window, or you can do it with KDE Connect indicator tray icon, there would be an entry for the pair request as well.</p>
<p>Otherwise, if you change the notification type of KDE Connect to <code>alert</code> in the system preference, you should also be able to do a quick action with the notification itself. Just as I showed in <a href="https://kde.inoki.cc/2019/07/18/KDE-Connect-macOS-plugin-notification/">Enable notification plugin in KDE Connect on macOS</a>.</p>
<p>Once paired, you can enjoy your adventure on macOS with KDE Connect!</p>
<h3 id="Clipboard-synchronization"><a href="#Clipboard-synchronization" class="headerlink" title="Clipboard synchronization"></a>Clipboard synchronization</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/Kdeconnect-clipboard-sync.png" title="KDE Connect Clipboard">
<p>The text that you copy on your Mac will be shared to your phone, and those you copy on your phone will be also synchronized to your Mac.</p>
<h3 id="Notification-synchronization"><a href="#Notification-synchronization" class="headerlink" title="Notification synchronization"></a>Notification synchronization</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/Kdeconnect-notification.png" title="KDE Connect Notification">
<p>With KNotifications support for macOS, you can receive notification from your Android phones and react to them. You can ping your Mac to test whether they are well connected.</p>
<h3 id="Sending-file"><a href="#Sending-file" class="headerlink" title="Sending file"></a>Sending file</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-SendFile.gif" title="KDE Connect Send file">
<p>Sharing your file on your Mac with your Android phone is also a basic feature. You could also send a file from your Android phone, by default, the file will be saved in the <code>Downloads</code> folder in your Mac.</p>
<h3 id="System-Volume"><a href="#System-Volume" class="headerlink" title="System Volume"></a>System Volume</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-Volume.gif" title="KDE Cnnect SFTP">
<p>You can control the system value of your Mac from your Android Phone remotely.</p>
<h3 id="SFTP"><a href="#SFTP" class="headerlink" title="SFTP"></a>SFTP</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-SFTPBrowser.gif" title="KDE Cnnect SFTP">
<p>With my SFTP browser, you can browse files in your Android Phone from your Mac, easily synchronize a file.</p>
<h3 id="SMS"><a href="#SMS" class="headerlink" title="SMS"></a>SMS</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-SMS.gif" title="KDE Connect SMS">
<p>Thanks to SMS application of Simon Redman, sending and receiving SMS on your Mac are possible!</p>
<h3 id="Running-command"><a href="#Running-command" class="headerlink" title="Running command"></a>Running command</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-RunCommand.gif" title="KDE Connect Run command">
<p>Run command from your Android phone. I believe that using AppleScript, more and more things that KDE Connect can do on macOS, will be discovered, maybe by you!</p>
<h3 id="Mouse-and-Keyboard"><a href="#Mouse-and-Keyboard" class="headerlink" title="Mouse and Keyboard"></a>Mouse and Keyboard</h3><img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/KDEConnect-macOS-OpenMouseKeyboard.gif" title="KDE Connect Allow control">
<p>You should be able to use your Android phone as a temporary trackpad and a keyboard. But it needs your permission to allow your Android phone to do it on your Mac. The GIF above shows how to do that.</p>
<h3 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h3><p>Except the functions shown above, you can also do these from your Android phone:</p>
<ul>
<li>Keep your Mac awake when your phone is connected</li>
<li>Use your phone to control your slides during a presentation</li>
<li>Check the battery level of your phone</li>
<li>Ring your phone to help find it</li>
</ul>
<p>And, you may have noticed that, in the screen capture, there are KDE Connect in dark mode and in light mode. Thanks to Qt, we are able to benefit it.</p>
<p>Furthermore, there is no doubt that more functions will be delivered and released in the future. We are all looking forward to them.</p>
<h1 id="Issues"><a href="#Issues" class="headerlink" title="Issues"></a>Issues</h1><p>There are some issues that we’ve known and we are trying to fix them.</p>
<img src="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/Gatekeeper.jpg" title="KDE Connect rejected by GateKeeper">
<p>The released application package isn’t notarized and still has some lirary reference issues. So, it requires you to manually open it, if it’s rejected by Gatekeeper(package validator on macOS), like that showed in the image above.</p>
<p>We’ll try to fix all issues and make a release which you can run it without barricade.</p>
<h1 id="Acknowledgement"><a href="#Acknowledgement" class="headerlink" title="Acknowledgement"></a>Acknowledgement</h1><p>Thanks to KDE Community and Google, I could finish this Google Summer of Code project this summer.</p>
<p>Thanks to members in KDE Connect development. Without them, I cannnot understand the mechanism and get it work on macOS so quickly :)</p>
<h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>If you have any question, <a href="https://community.kde.org/KDEConnect" target="_blank" rel="noopener">KDE Connect Wiki</a> may be helpful. And you can find a bug tracker there.</p>
<p>Don’t be hesitated to join our Telegram Group or IRC channel if you’d like to bring more exciting functions into <code>KDE Connect</code>: </p>
<ul>
<li><a href="https://t.me/joinchat/BRUUN0bwMhNfn8FIejA-nw" target="_blank" rel="noopener">Telegram</a></li>
<li>IRC (#kdeconnect)</li>
<li>matrix.org (#freenode_#kdeconnect:matrix.org)</li>
</ul>
<p>I wish you could enjoy the seamless experience provided by <code>KDE Connect</code> for macOS and your Android Phone!</p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-09-01T14:43:25.000Z">2019-09-01</time>
	</a>
</span>

	<span class="cat-links font-asesome-icon">
		
				<a href="/categories/KDE-Connect/" rel="category tag">KDE Connect</a>
		
				<a href="/categories/KDE-Connect/macOS/" rel="category tag">macOS</a>
		
	</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/macOS/" rel="category tag">macOS</a>
    
		<a href="/tags/KDE-Connect/" rel="category tag">KDE Connect</a>
    
		<a href="/tags/GSoC/" rel="category tag">GSoC</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/09/01/KDE-Connect-macOS-GSoC-Final-Release/" class="leancloud_visitors" data-flag-title="KDE Connect macOS Release">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/07/24/DBus/" rel="bookmark">DBus connection on macOS</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<h1 id="What-is-DBus"><a href="#What-is-DBus" class="headerlink" title="What is DBus"></a>What is DBus</h1><p>DBus is a concept of software bus, an inter-process communication (IPC), and a remote procedure call (RPC) mechanism that allows communication between multiple computer programs (that is, processes) concurrently running on the same machine. DBus was developed as part of the freedesktop.org project, initiated by Havoc Pennington from Red Hat to standardize services provided by Linux desktop environments such as GNOME and KDE.</p>
<p>In this post, we only talk about how does DBus daemon run and how KDE Applications/Frameworks connect to it. For more details of DBus itself, please move to <a href="https://www.freedesktop.org/wiki/Software/dbus/" target="_blank" rel="noopener">DBus Wiki</a>.</p>
<h1 id="QDBus"><a href="#QDBus" class="headerlink" title="QDBus"></a>QDBus</h1><p>There are two types of bus: <code>session</code> bus and <code>system</code> bus. The user-end applications should use <code>session</code> bus for IPC or RPC.</p>
<p>For the DBus connection, there is already a good enough library named QDBus provided by Qt. Qt framework and especially QDBus is widely used in KDE Applications and Frameworks on Linux.</p>
<p>A mostly used function is <code>QDBusConnection::sessionBus()</code> to establish a connection to default session DBus. All DBus connection are established through this function.</p>
<p>Its implementation is:<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">QDBusConnection QDBusConnection::sessionBus()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (_q_manager.isDestroyed())</span><br><span class="line">        <span class="keyword">return</span> QDBusConnection(<span class="literal">nullptr</span>);</span><br><span class="line">    <span class="keyword">return</span> QDBusConnection(_q_manager()-&gt;busConnection(SessionBus));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>where <code>_q_manager</code> is an instance of <code>QDBusConnectionManager</code>.</p>
<p><code>QDBusConnectionManager</code> is a private class so that we don’t know what exactly happens in the implementation.</p>
<p>The code can be found in <a href="https://github.com/qt/qtbase/blob/589d96b9b06a4a7d0dca03a06c80716318761277/src/dbus/qdbusconnection.cpp#L1175" target="_blank" rel="noopener">qtbase</a>.</p>
<h1 id="DBus-connection-on-macOS"><a href="#DBus-connection-on-macOS" class="headerlink" title="DBus connection on macOS"></a>DBus connection on macOS</h1><p>On macOS, we don’t have a pre-installed dbus. When we compile it from source code, or install it from HomeBrew or somewhere, a configuration file <code>session.conf</code> and a <code>launchd</code> configuration file <code>org.freedesktop.dbus-session.plist</code> are delivered and expected to install into the system.</p>
<h2 id="session-conf"><a href="#session-conf" class="headerlink" title="session.conf"></a>session.conf</h2><p>In <code>session.conf</code>, one important thing is <code>&lt;listen&gt;launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET&lt;/listen&gt;</code>, which means socket path should be provided by <code>launchd</code> through the environment <code>DBUS_LAUNCHD_SESSION_BUS_SOCKET</code>.</p>
<h2 id="org-freedesktop-dbus-session-plist"><a href="#org-freedesktop-dbus-session-plist" class="headerlink" title="org.freedesktop.dbus-session.plist"></a>org.freedesktop.dbus-session.plist</h2><p>On macOS, <code>launchd</code> is a unified operating system service management framework, starts, stops and manages daemons, applications, processes, and scripts. Just like <code>systemd</code> on Linux.</p>
<p>The file <code>org.freedesktop.dbus-session.plist</code> describes how <code>launchd</code> can find a daemon executable, the arguments to launch it, and the socket to communicate after launching daemon.</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">"1.0"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>Label<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">string</span>&gt;</span>org.freedesktop.dbus-session<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>ProgramArguments<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">string</span>&gt;</span>/&#123;DBus install path&#125;/bin/dbus-daemon<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">string</span>&gt;</span>--nofork<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">string</span>&gt;</span>--session<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>Sockets<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">key</span>&gt;</span>unix_domain_listener<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">			<span class="tag">&lt;<span class="name">key</span>&gt;</span>SecureSocketWithKey<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">			<span class="tag">&lt;<span class="name">string</span>&gt;</span>DBUS_LAUNCHD_SESSION_BUS_SOCKET<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>Once the daemon is successfully launched by <code>launchd</code>, the socket will be provided in <code>DBUS_LAUNCHD_SESSION_BUS_SOCKET</code> env of <code>launchd</code>.</p>
<p>We can get it with following command:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET</span><br></pre></td></tr></table></figure></p>
<h1 id="Current-solution-in-KDE-Connect"><a href="#Current-solution-in-KDE-Connect" class="headerlink" title="Current solution in KDE Connect"></a>Current solution in KDE Connect</h1><p>KDE Connect needs urgently DBus to make, the communication between <code>kdeconenctd</code> and <code>kdeconnect-indicator</code> or other components, possible.</p>
<h2 id="First-try"><a href="#First-try" class="headerlink" title="First try"></a>First try</h2><p>Currently, we delivered <code>dbus-daemon</code> in the package, and run</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./Contents/MacOS/dbus-daemon --config-file=./Contents/Resources/dbus-1/session.conf --<span class="built_in">print</span>-address --nofork --address=unix:tmpdir=/tmp</span><br></pre></td></tr></table></figure>
<p><code>--address=unix:tmpdir=/tmp</code> provides a base directory to store a random unix socket descriptor. So we could have serveral instances at the same time, with different addresse.</p>
<p><code>--print-address</code> can let <code>dbus-daemon</code> write its generated, real address into standard output. </p>
<p>Then we redirect the output of <code>dbus-daemon</code> to<br><code>KdeConnectConfig::instance()-&gt;privateDBusAddressPath()</code>. Normally, it should be <code>$HOME/Library/Preferences/kdeconnect/private_dbus_address</code>. For example, the address in it is <code>unix:path=/tmp/dbus-K0TrkEKiEB,guid=27b519a52f4f9abdcb8848165d3733a6</code>.</p>
<p>Therefore, our program can access this file to get the real DBus address, and use another function in QDBus to connect to it:<br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">QDBusConnection::connectToBus(KdeConnectConfig::instance()-&gt;privateDBusAddress(), QStringLiteral(KDECONNECT_PRIVATE_DBUS_NAME));</span><br></pre></td></tr></table></figure></p>
<p>We redirect all <code>QDBusConnection::sessionBus</code> to <code>QDBusConnection::connectToBus</code> to connect to our own DBus.</p>
<h2 id="Fake-a-session-DBus"><a href="#Fake-a-session-DBus" class="headerlink" title="Fake a session DBus"></a>Fake a session DBus</h2><p>With such solution, <code>kdeconnectd</code> and <code>kdeconnect-indicator</code> coworks well. But in KFrameworks, there are lots of components which are using <code>QDBusConnection::sessionBus</code> rather than <code>QDBusConnection::connectToBus</code>. We cannot change all of them.</p>
<p>Then I came up with an idea, try to <strong>fake a session bus</strong> on macOS.</p>
<p>To hack and validate, I tried to launch a <code>dbus-daemon</code> using <code>/tmp/dbus-K0TrkEKiEB</code> as address, and then I tried type this in my terminal:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">launchctl setenv DBUS_LAUNCHD_SESSION_BUS_SOCKET /tmp/dbus-K0TrkEKiEB</span><br></pre></td></tr></table></figure></p>
<p>Then I launched <code>dbus-monitor --session</code>. It did connect to the bus that I launched.</p>
<p>And then, any <code>QDBusConnection::sessionBus</code> can establish a stable connection to the faked session bus. So components in KFramework can use the same session bus as well.</p>
<p>To implement it in KDE Connect, after starting <code>dbus-daemon</code>, I read the file content, filter the socket address, and call <code>launchctl</code> to set <code>DBUS_LAUNCHD_SESSION_BUS_SOCKET</code> env.</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Set launchctl env</span></span><br><span class="line">QString privateDBusAddress = KdeConnectConfig::instance()-&gt;privateDBusAddress();</span><br><span class="line">QRegularExpressionMatch path;</span><br><span class="line"><span class="keyword">if</span> (privateDBusAddress.contains(QRegularExpression(</span><br><span class="line">        QStringLiteral(<span class="string">"path=(?&lt;path&gt;/tmp/dbus-[A-Za-z0-9]+)"</span>)</span><br><span class="line">    ), &amp;path)) &#123;</span><br><span class="line">    qCDebug(KDECONNECT_CORE) &lt;&lt; <span class="string">"DBus address: "</span> &lt;&lt; path.captured(QStringLiteral(<span class="string">"path"</span>));</span><br><span class="line">    QProcess setLaunchdDBusEnv;</span><br><span class="line">    setLaunchdDBusEnv.setProgram(QStringLiteral(<span class="string">"launchctl"</span>));</span><br><span class="line">    setLaunchdDBusEnv.setArguments(&#123;</span><br><span class="line">        QStringLiteral(<span class="string">"setenv"</span>),</span><br><span class="line">        QStringLiteral(KDECONNECT_SESSION_DBUS_LAUNCHD_ENV),</span><br><span class="line">        path.captured(QStringLiteral(<span class="string">"path"</span>))</span><br><span class="line">    &#125;);</span><br><span class="line">    setLaunchdDBusEnv.start();</span><br><span class="line">    setLaunchdDBusEnv.waitForFinished();</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    qCDebug(KDECONNECT_CORE) &lt;&lt; <span class="string">"Cannot get dbus address"</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Then everything works!</p>
<h2 id="Possible-improvement"><a href="#Possible-improvement" class="headerlink" title="Possible improvement"></a>Possible improvement</h2><ol>
<li>Since we can directly use session bus, the redirect from <code>QDBusConnection::sessionBus</code> to <code>QDBusConnection::connectToBus</code> is not necessary anymore. Everyone can connect it in convenience.</li>
<li>Each time we launch <code>kdeconnectd</code>, a new <code>dbus-daemon</code> is launched and the environment in <code>launchctl</code> is overwritten. To improve this, we might detect whether there is already an available <code>dbus-daemon</code> through testing connectivity of returned <code>QDBusConnection::sessionBus</code>. This might be done by a bootstrap script.</li>
<li>It will be really nice if we can have a unified way for all KDE Applications on macOS.</li>
</ol>
<h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>I’m looking forward to a general DBus solution for all KDE applications :)</p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-07-24T14:55:00.000Z">2019-07-24</time>
	</a>
</span>

	<span class="cat-links font-asesome-icon">
		
				<a href="/categories/macOS/" rel="category tag">macOS</a>
		
	</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/macOS/" rel="category tag">macOS</a>
    
		<a href="/tags/DBus/" rel="category tag">DBus</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/07/24/DBus/" class="leancloud_visitors" data-flag-title="DBus connection on macOS">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/07/18/KDE-Connect-macOS-plugin-notification/" rel="bookmark">Enable notification plugin in KDE Connect on macOS</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<p>You may have tried KDE Connect for macOS. </p>
<p>If you’ve not yet tried KDE Connect, you can read my post: <a href="/2019/07/16/KDE-Connect-macOS/">Connect your Android phone with your Mac via KDE Connect</a></p>
<p>As I mentioned, this post will help you to build your own KDE Connect with native Notification support for macOS.</p>
<img src="/2019/07/18/KDE-Connect-macOS-plugin-notification/notification-preview.gif" title="Notification Preview">
<h1 id="Build"><a href="#Build" class="headerlink" title="Build"></a>Build</h1><p>This post will not give you instructions of building KDE Connect on macOS because there is already a page on <a href="https://community.kde.org/KDEConnect/Build_MacOS" target="_blank" rel="noopener">KDE Connect Wiki</a></p>
<p>If you met any problems, you can submit them on our <a href="https://bugs.kde.org/enter_bug.cgi?product=kdeconnect" target="_blank" rel="noopener">KDE bug tracker</a></p>
<h1 id="Add-notification-support"><a href="#Add-notification-support" class="headerlink" title="Add notification support"></a>Add notification support</h1><p>Notification plugin depends on <code>KNotification</code>. There is no native support for macOS in this library. </p>
<p>I’ve made a native one and it has been submited as a patch. But it takes time to get reviewed and optimized.</p>
<p>I keep the patch available on a repo of my GitHub:<br><a href="https://github.com/Inokinoki/knotifications" target="_blank" rel="noopener">https://github.com/Inokinoki/knotifications</a>. So, <code>Craft</code> can access it and compile it to provide support of macOS Notification.</p>
<p>But we’re looking forward to its delivery in <code>KNotification</code>.</p>
<p>What you need to do is very simple:</p>
<ol>
<li>Find KNotifications blueprint file</li>
</ol>
<ul>
<li>Enter your <code>CraftRoot</code> folder. To me, it’s <code>/Users/inoki/CraftRoot</code>. </li>
<li>Enter <code>etc</code> -&gt; <code>blueprints</code> -&gt; <code>locations</code> -&gt; <code>craft-blueprints-kde</code> folder.</li>
<li>Open <code>kde/frameworks/tier3/knotifications/knotifications.py</code>.</li>
</ul>
<ol start="2">
<li><p>Remove <code>self.versionInfo.setDefaultValues()</code> in <code>setTargets</code> of <code>subinfo</code> class. If you’re not familiar with <code>python</code>, just find this line and delete it.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">self.versionInfo.setDefaultValues()</span><br></pre></td></tr></table></figure>
</li>
<li><p>Add these 2 lines:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">self.svnTargets[<span class="string">'master'</span>] = <span class="string">'https://github.com/Inokinoki/knotifications.git'</span></span><br><span class="line">self.defaultTarget = <span class="string">'master'</span></span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>The file should look like this:</p>
<img src="/2019/07/18/KDE-Connect-macOS-plugin-notification/preview.png" title="knotification blueprint preview">
<p>After that, rebuild KDE Connect with Craft.</p>
<p>If everything is ok, launch your KDE Connect.</p>
<p>You could receive notifications from your phone or your other computers(if well configured), just like this:</p>
<img src="/2019/07/18/KDE-Connect-macOS-plugin-notification/notification.png" title="Test Notification">
<p>You can also change notification settings of KDE Connect in your macOS Notification Center. By default, the notification style is <code>Bar</code>, set it to <code>Alert</code> to see quick actions to your notifications.</p>
<h4 id="Notice-Currently-there-is-a-bug-you-may-receive-duplicated-notifications-We’re-figuring-out-its-reason-and-it-will-be-fixed-as-soon-as-possible"><a href="#Notice-Currently-there-is-a-bug-you-may-receive-duplicated-notifications-We’re-figuring-out-its-reason-and-it-will-be-fixed-as-soon-as-possible" class="headerlink" title="Notice: Currently there is a bug, you may receive duplicated notifications. We’re figuring out its reason and it will be fixed as soon as possible."></a>Notice: Currently there is a bug, you may receive duplicated notifications. We’re figuring out its reason and it will be fixed as soon as possible.</h4><p>Thanks for your reading and your support to KDE Connect :)</p>
<p>If you’d like to, you can also follow me on GitHub :)</p>
<h1 id="For-pros"><a href="#For-pros" class="headerlink" title="For pros"></a>For pros</h1><p>For developers, if you’re familiar with diff, just apply this diff patch:<br><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">diff --git a/kde/frameworks/tier3/knotifications/knotifications.py b/kde/frameworks/tier3/knotifications/knotifications.py</span><br><span class="line">index 9b46044..f5c82a4 100644</span><br><span class="line"><span class="comment">--- a/kde/frameworks/tier3/knotifications/knotifications.py</span></span><br><span class="line"><span class="comment">+++ b/kde/frameworks/tier3/knotifications/knotifications.py</span></span><br><span class="line">@@ -3,7 +3,8 @@ import info</span><br><span class="line"> </span><br><span class="line"> class subinfo(info.infoclass):</span><br><span class="line">     def setTargets(self):</span><br><span class="line"><span class="deletion">-        self.versionInfo.setDefaultValues()</span></span><br><span class="line"><span class="addition">+        self.svnTargets['master'] = 'https://github.com/Inokinoki/knotifications.git'</span></span><br><span class="line"><span class="addition">+        self.defaultTarget = 'master'</span></span><br><span class="line">         self.patchToApply['5.57.0'] = [("disabled-deprecated-before.patch", 1)]</span><br><span class="line"> </span><br><span class="line">         self.description = "TODO"</span><br></pre></td></tr></table></figure></p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-07-18T14:09:22.000Z">2019-07-18</time>
	</a>
</span>

	<span class="cat-links font-asesome-icon">
		
				<a href="/categories/KDE-Connect/" rel="category tag">KDE Connect</a>
		
				<a href="/categories/KDE-Connect/macOS/" rel="category tag">macOS</a>
		
	</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/macOS/" rel="category tag">macOS</a>
    
		<a href="/tags/KDE-Connect/" rel="category tag">KDE Connect</a>
    
		<a href="/tags/GSoC/" rel="category tag">GSoC</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/07/18/KDE-Connect-macOS-plugin-notification/" class="leancloud_visitors" data-flag-title="Enable notification plugin in KDE Connect on macOS">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/07/16/KDE-Connect-macOS/" rel="bookmark">Connect your Android phone with your Mac via KDE Connect</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<p>Have you ever heard <a href="https://www.apple.com/macos/continuity/" target="_blank" rel="noopener">Continuity</a>, the solution of Apple which provides one seamless experience between your iPhone and your Mac?</p>
<img src="/2019/07/16/KDE-Connect-macOS/apple-continuity.png" title="Apple Continuity">
<p>You may be surprised, “Woohoo, it’s amazing but I use my OnePlus along with my Mac.” With my GSoC 2019 project, you can connect your Mac and your Android phone with <code>KDE Connect</code>!</p>
<p>And you can even connect your Mac with your Linux PC or Windows PC (Thanks to Piyush, he is working on optimizing experience of <code>KDE Connect</code> on Windows).</p>
<h1 id="Installation-instruction"><a href="#Installation-instruction" class="headerlink" title="Installation instruction"></a>Installation instruction</h1><ol>
<li><p>You can download <code>KDE Connect</code> Nightly Build for macOS from KDE Binary Factory: <a href="https://binary-factory.kde.org/view/MacOS/job/kdeconnect-kde_Nightly_macos/" target="_blank" rel="noopener">https://binary-factory.kde.org/view/MacOS/job/kdeconnect-kde_Nightly_macos/</a>. But notice that it’s not yet a stable version, and it requires that you have permission to run application from non-certificated developer. We’ll release a stable one next month on August.</p>
</li>
<li><p>Otherwise you can build your own version. Please follow the instructions on <a href="https://community.kde.org/KDEConnect/Build_MacOS" target="_blank" rel="noopener">KDE Connect Wiki</a>. If you’re using <code>macOS 10.13</code>, <code>MacOS X 10.12</code> or below, we recommend that you build your own <code>KDE Connect</code> because our Binary Factory are building applications for only <code>macOS 10.14</code> or above.</p>
</li>
</ol>
<p>You’ll finally get a <code>DMG</code> image file in both 2 ways.</p>
<img src="/2019/07/16/KDE-Connect-macOS/dmg.png" title="KDE Connect DMG image">
<p>Just click on it, mount it and drap <code>kdeconnect-indicator</code> into <code>Applications</code> folder. </p>
<p>Open <code>kdeconnect-indicator</code> and your magic journey with <code>KDE Connect</code> for macOS begins!</p>
<h1 id="Use"><a href="#Use" class="headerlink" title="Use"></a>Use</h1><p>After installation, you can see an icon of <code>kdeconnect-indicator</code> in the Launchpad.</p>
<img src="/2019/07/16/KDE-Connect-macOS/launchpad.png" title="KDE Connect in LaunchPad">
<p>Click it to open. If everything is ok, you will see an <code>KDE Connect</code> icon in your system tray.</p>
<img src="/2019/07/16/KDE-Connect-macOS/trayicon.png" title="KDE Connect in System Tray">
<p>Click the icon -&gt; Configure to open configuration window. Here you can see discovered devices and paired devices.</p>
<img src="/2019/07/16/KDE-Connect-macOS/configuration.png" title="KDE Connect Configuration Window">
<p>You can enable or disable functions in this window.</p>
<p>Currently, you can do these from your Android phone:</p>
<ul>
<li>Run predefined commands on your Mac from connected devices.</li>
<li>Check your phones battery level from the desktop</li>
<li>Ring your phone to help finding it</li>
<li>Share files and links between devices</li>
<li>Control the volume of your Mac from the phone</li>
<li>Keep your Mac awake when your phone is connected</li>
<li>Receive your phone notifications on your desktop computer (this function is achieved but not yet delivered, you can follow <a href="/2019/07/18/KDE-Connect-macOS-plugin-notification/">this post</a> to enable it manually)</li>
</ul>
<p>I’m trying to make more plugins work on macOS. Good luck to my GSoC project :)</p>
<h1 id="Acknowledgement"><a href="#Acknowledgement" class="headerlink" title="Acknowledgement"></a>Acknowledgement</h1><p>Thanks to KDE Community and Google, I could start this Google Summer of Code project this summer.</p>
<p>Thanks to members in KDE Connect development. Without them, I cannnot understand the mechanism and get it work on macOS so quickly :)</p>
<h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>If you have any question, <a href="https://community.kde.org/KDEConnect" target="_blank" rel="noopener">KDE Connect Wiki</a> may be helpful. And you can find a bug tracker there.</p>
<p>Don’t be hesitated to join our Telegram Group or IRC channel if you’d like to bring more exciting functions into <code>KDE Connect</code>: </p>
<ul>
<li><a href="https://t.me/joinchat/BRUUN0bwMhNfn8FIejA-nw" target="_blank" rel="noopener">Telegram</a></li>
<li>IRC (#kdeconnect)</li>
<li>matrix.org (#freenode_#kdeconnect:matrix.org)</li>
</ul>
<p>I wish you could enjoy the seamless experience provided by <code>KDE Connect</code> for macOS and your Android Phone!</p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-07-16T11:57:22.000Z">2019-07-16</time>
	</a>
</span>

	<span class="cat-links font-asesome-icon">
		
				<a href="/categories/KDE-Connect/" rel="category tag">KDE Connect</a>
		
				<a href="/categories/KDE-Connect/macOS/" rel="category tag">macOS</a>
		
	</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/macOS/" rel="category tag">macOS</a>
    
		<a href="/tags/KDE-Connect/" rel="category tag">KDE Connect</a>
    
		<a href="/tags/GSoC/" rel="category tag">GSoC</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/07/16/KDE-Connect-macOS/" class="leancloud_visitors" data-flag-title="Connect your Android phone with your Mac via KDE Connect">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/05/26/Craft-packager/" rel="bookmark">KDE Craft Packager on macOS</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<p>In Craft, to create a package, we can use <code>craft --package &lt;blueprint-name&gt;</code> after the compiling and the installing of a library or an application with given blueprint name.</p>
<p>On macOS, <code>MacDMGPackager</code> is the packager used by Craft. The <code>MacDylibBundler</code>is used in <code>MacDMGPackager</code> to handle the dependencies.</p>
<p>In this article, I’ll give a brief introduction of the two classes and the improvement which I’ve done for my GSoC project.</p>
<h1 id="MacDMGPackager"><a href="#MacDMGPackager" class="headerlink" title="MacDMGPackager"></a>MacDMGPackager</h1><p><code>MacDMGPackager</code> is a subclass of <code>CollectionPackagerBase</code>. Its most important method is <code>createPackage</code>.</p>
<p>First of all, </p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">self.internalCreatePackage(seperateSymbolFiles=packageSymbols)</span><br></pre></td></tr></table></figure>
<h2 id="Initialisation-of-directory-variables"><a href="#Initialisation-of-directory-variables" class="headerlink" title="Initialisation of directory variables"></a>Initialisation of directory variables</h2><p>Here we get the definitions, the path of the application which we want to pack, and the path of archive.<br>The <code>appPath</code> should be the root of an application package with <code>.app</code> extension name. According to the convention of applications on macOS, <code>targetLibdir</code> points to the library directory of the application.<br>During the compiling and the installing period, in the application directory, there is only a <code>.plist</code> and <code>MacOS</code> subdirectory. So next, the library directory is created for further using.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">defines = self.setDefaults(self.defines)</span><br><span class="line">appPath = self.getMacAppPath(defines)</span><br><span class="line">archive = os.path.normpath(self.archiveDir())</span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line">targetLibdir = os.path.join(appPath, <span class="string">"Contents"</span>, <span class="string">"Frameworks"</span>)</span><br><span class="line">utils.createDir(targetLibdir)</span><br></pre></td></tr></table></figure>
<h2 id="Moving-files-to-correct-directories"><a href="#Moving-files-to-correct-directories" class="headerlink" title="Moving files to correct directories"></a>Moving files to correct directories</h2><p>Then, we predefine a list of pairs of source and destination for directories and move the files to the destinations. The destionations are the correct directories of libraries, plugins and resources in a macOS application package.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">moveTargets = [</span><br><span class="line">    (os.path.join(archive, <span class="string">"lib"</span>, <span class="string">"plugins"</span>), os.path.join(appPath, <span class="string">"Contents"</span>, <span class="string">"PlugIns"</span>)),</span><br><span class="line">    (os.path.join(archive, <span class="string">"plugins"</span>), os.path.join(appPath, <span class="string">"Contents"</span>, <span class="string">"PlugIns"</span>)),</span><br><span class="line">    (os.path.join(archive, <span class="string">"lib"</span>), targetLibdir),</span><br><span class="line">    (os.path.join(archive, <span class="string">"share"</span>), os.path.join(appPath, <span class="string">"Contents"</span>, <span class="string">"Resources"</span>))]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> appPath.startswith(archive):</span><br><span class="line">    moveTargets += [(os.path.join(archive, <span class="string">"bin"</span>), os.path.join(appPath, <span class="string">"Contents"</span>, <span class="string">"MacOS"</span>))]</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> src, dest <span class="keyword">in</span> moveTargets:</span><br><span class="line">    <span class="keyword">if</span> os.path.exists(src):</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> utils.mergeTree(src, dest):</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>
<h2 id="Fixing-dependencies-using-MacDylibBundler"><a href="#Fixing-dependencies-using-MacDylibBundler" class="headerlink" title="Fixing dependencies using MacDylibBundler"></a>Fixing dependencies using MacDylibBundler</h2><p>After the moving, we create an instance of <code>MacDylibBundler</code> with <code>appPath</code>. After the <code>with</code> instruction, all the codes are executed with <code>DYLD_FALLBACK_LIBRARY_PATH=&lt;package.app&gt;/Contents/Frameworks:&lt;Craft-Root&gt;/lib</code> environment variable.</p>
<p>For further reading of this environment variable, please refer this question on <a href="https://stackoverflow.com/questions/3146274/is-it-ok-to-use-dyld-library-path-on-mac-os-x-and-whats-the-dynamic-library-s" target="_blank" rel="noopener">StackOverFlow</a>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dylibbundler = MacDylibBundler(appPath)</span><br><span class="line"><span class="keyword">with</span> utils.ScopedEnv(&#123;<span class="string">'DYLD_FALLBACK_LIBRARY_PATH'</span>: targetLibdir + <span class="string">":"</span> + os.path.join(CraftStandardDirs.craftRoot(), <span class="string">"lib"</span>)&#125;):</span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<h3 id="Fixing-dependencies-of-main-binary"><a href="#Fixing-dependencies-of-main-binary" class="headerlink" title="Fixing dependencies of main binary"></a>Fixing dependencies of main binary</h3><p>Here, we firstly create an object of Path. It points to the executable of macOS Package.</p>
<p>It should be reminded that, although here, we use the same name for both the macOS application package and the executable, it is not mandatory. The name of executable is defined by <code>CFBundleExecutable</code> in the <code>.plist</code> file. So maybe read it from the <code>.plist</code> file is a better solution.</p>
<p>Then, the method <code>bundleLibraryDependencies</code> is used to copy libraries and fix dependencies for the executable in the package.</p>
<p>A brief introduction of this method:</p>
<ol>
<li>Call <code>utils.getLibraryDeps</code> for getting a list of dependencies. This operation is done by using <code>otool -L</code>.</li>
<li>Copy missing dependencies into <code>Contents/Frameworks</code>, and update the library information in the executable.<br>I’ll give an analyse in detail in the next chapter.</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">CraftCore.log.info(<span class="string">"Bundling main binary dependencies..."</span>)</span><br><span class="line">mainBinary = Path(appPath, <span class="string">"Contents"</span>, <span class="string">"MacOS"</span>, defines[<span class="string">'appname'</span>])</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.bundleLibraryDependencies(mainBinary):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>
<h3 id="Fixing-dependencies-in-Frameworks-and-PlugIns"><a href="#Fixing-dependencies-in-Frameworks-and-PlugIns" class="headerlink" title="Fixing dependencies in Frameworks and PlugIns"></a>Fixing dependencies in Frameworks and PlugIns</h3><p>And then, we try to fix all the dependencies of libraries in <code>Contents/Frameworks</code> and <code>Contents/PlugIns</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Fix up the library dependencies of files in Contents/Frameworks/</span></span><br><span class="line">CraftCore.log.info(<span class="string">"Bundling library dependencies..."</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.fixupAndBundleLibsRecursively(<span class="string">"Contents/Frameworks"</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">CraftCore.log.info(<span class="string">"Bundling plugin dependencies..."</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.fixupAndBundleLibsRecursively(<span class="string">"Contents/PlugIns"</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>
<h3 id="Fixing-dependencies-using-macdeployqt"><a href="#Fixing-dependencies-using-macdeployqt" class="headerlink" title="Fixing dependencies using macdeployqt"></a>Fixing dependencies using macdeployqt</h3><p>The <code>macdeployqt</code> is used to fix the <code>Qt</code> libraries used by the application. <code>Craft</code> installed it while compiling and installing <code>Qt</code>. But don’t worry, it is not in your system path.</p>
<p>I have not yet found what <code>macdeployqt</code> exactly do, it’s nice to have an look at its source code.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> utils.system([<span class="string">"macdeployqt"</span>, appPath, <span class="string">"-always-overwrite"</span>, <span class="string">"-verbose=1"</span>]):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>
<h3 id="Removing-files-in-blacklist"><a href="#Removing-files-in-blacklist" class="headerlink" title="Removing files in blacklist"></a>Removing files in blacklist</h3><p>If <code>macdeplyqt</code> added some files which we don’t want, they would be removed here.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># macdeployqt might just have added some explicitly blacklisted files</span></span><br><span class="line">blackList = Path(self.packageDir(), <span class="string">"mac_blacklist.txt"</span>)</span><br><span class="line"><span class="keyword">if</span> blackList.exists():</span><br><span class="line">    pattern = [self.read_blacklist(str(blackList))]</span><br><span class="line">    <span class="comment"># use it as whitelist as we want only matches, ignore all others</span></span><br><span class="line">    matches = utils.filterDirectoryContent(appPath, whitelist=<span class="keyword">lambda</span> x, root: utils.regexFileFilter(x, root, pattern), blacklist=<span class="keyword">lambda</span> x, root:<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">for</span> f <span class="keyword">in</span> matches:</span><br><span class="line">        CraftCore.log.info(<span class="string">f"Remove blacklisted file: <span class="subst">&#123;f&#125;</span>"</span>)</span><br><span class="line">        utils.deleteFile(f)</span><br></pre></td></tr></table></figure>
<h3 id="Fixing-dependencies-after-fixing-of-macdeployqt"><a href="#Fixing-dependencies-after-fixing-of-macdeployqt" class="headerlink" title="Fixing dependencies after fixing of macdeployqt"></a>Fixing dependencies after fixing of macdeployqt</h3><p>After <code>macdeplotqt</code>, there may be some libraries or plugins added by <code>macdeplotqt</code>. So we do the fixing of dependencies once again.</p>
<p>But I’m doubting if we need to fix twice the dependencies. I’ll update this post after I figure out what will it lead to if we fust fix after <code>macdeployqt</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># macdeployqt adds some more plugins so we fix the plugins after calling macdeployqt</span></span><br><span class="line">dylibbundler.checkedLibs = set()  <span class="comment"># ensure we check all libs again (but</span></span><br><span class="line"><span class="comment"># we should not need to make any changes)</span></span><br><span class="line">CraftCore.log.info(<span class="string">"Fixing plugin dependencies after macdeployqt..."</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.fixupAndBundleLibsRecursively(<span class="string">"Contents/PlugIns"</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">CraftCore.log.info(<span class="string">"Fixing library dependencies after macdeployqt..."</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.fixupAndBundleLibsRecursively(<span class="string">"Contents/Frameworks"</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>
<h3 id="Checking-dependencies"><a href="#Checking-dependencies" class="headerlink" title="Checking dependencies"></a>Checking dependencies</h3><p>Then, we use <code>MacDylibBundler</code> to check all dependencies in the application package. If there is any bad dependency, the package process will fail.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Finally sanity check that we don't depend on absolute paths from the builder</span></span><br><span class="line">CraftCore.log.info(<span class="string">"Checking for absolute library paths in package..."</span>)</span><br><span class="line">found_bad_dylib = <span class="literal">False</span>  <span class="comment"># Don't exit immeditately so that we log all the bad libraries before failing:</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.areLibraryDepsOkay(mainBinary):</span><br><span class="line">    found_bad_dylib = <span class="literal">True</span></span><br><span class="line">    CraftCore.log.error(<span class="string">"Found bad library dependency in main binary %s"</span>, mainBinary)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.checkLibraryDepsRecursively(<span class="string">"Contents/Frameworks"</span>):</span><br><span class="line">    CraftCore.log.error(<span class="string">"Found bad library dependency in bundled libraries"</span>)</span><br><span class="line">    found_bad_dylib = <span class="literal">True</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> dylibbundler.checkLibraryDepsRecursively(<span class="string">"Contents/PlugIns"</span>):</span><br><span class="line">    CraftCore.log.error(<span class="string">"Found bad library dependency in bundled plugins"</span>)</span><br><span class="line">    found_bad_dylib = <span class="literal">True</span></span><br><span class="line"><span class="keyword">if</span> found_bad_dylib:</span><br><span class="line">    CraftCore.log.error(<span class="string">"Cannot not create .dmg since the .app contains a bad library depenency!"</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>
<h2 id="Creating-DMG-image"><a href="#Creating-DMG-image" class="headerlink" title="Creating DMG image"></a>Creating DMG image</h2><p>Up to now, everything is well, we can create a DMG image for the application.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">name = self.binaryArchiveName(fileType=<span class="string">""</span>, includeRevision=<span class="literal">True</span>)</span><br><span class="line">dmgDest = os.path.join(self.packageDestinationDir(), <span class="string">f"<span class="subst">&#123;name&#125;</span>.dmg"</span>)</span><br><span class="line"><span class="keyword">if</span> os.path.exists(dmgDest):</span><br><span class="line">    utils.deleteFile(dmgDest)</span><br><span class="line">appName = defines[<span class="string">'appname'</span>] + <span class="string">".app"</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> utils.system([<span class="string">"create-dmg"</span>, <span class="string">"--volname"</span>, name,</span><br><span class="line">                        <span class="comment"># Add a drop link to /Applications:</span></span><br><span class="line">                        <span class="string">"--icon"</span>, appName, <span class="string">"140"</span>, <span class="string">"150"</span>, <span class="string">"--app-drop-link"</span>, <span class="string">"350"</span>, <span class="string">"150"</span>,</span><br><span class="line">                        dmgDest, appPath]):</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">CraftHash.createDigestFiles(dmgDest)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<p>An example of <code>DMG</code> image is like this one, users can drag the application into <code>Applications</code> directory to install it.</p>
<img src="/2019/05/26/Craft-packager/DMG-example.png" title="a DMG file">
<h1 id="MacDylibBundler"><a href="#MacDylibBundler" class="headerlink" title="MacDylibBundler"></a>MacDylibBundler</h1><h2 id="Constructor"><a href="#Constructor" class="headerlink" title="Constructor"></a>Constructor</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, appPath: str)</span>:</span></span><br><span class="line">    <span class="comment"># Avoid processing the same file more than once</span></span><br><span class="line">    self.checkedLibs = set()</span><br><span class="line">    self.appPath = appPath</span><br></pre></td></tr></table></figure>
<p>In the constructor, a set is created to store the libraries which have been already checked. And the <code>appPath</code> passed by developer is stored.</p>
<h1 id="Methods"><a href="#Methods" class="headerlink" title="Methods"></a>Methods</h1><p>This method <code>bundleLibraryDependencies</code> and <code>_addLibToAppImage</code> are the most important methods in this class. But they’re too long. So I’ll only give some brief introduction of them.</p>
<p><code>_addLibToAppImage</code> checks whether a library is already in the <code>Contents/Frameworks</code>. If the library doesn’t exist, it copies it into the diretory and tries to fix it with some relative path.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_addLibToAppImage</span><span class="params">(self, libPath: Path)</span> -&gt; bool:</span></span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<p><code>bundleLibraryDependencies</code> checks the dependencies of <code>fileToFix</code>. If there are some dependencies with absolute path, it copies the dependencies into <code>Contents/Frameworks</code> by calling <code>_addLibToAppImage</code>. And then, it calls <code>_updateLibraryReference</code> to update the reference of library.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">bundleLibraryDependencies</span><span class="params">(self, fileToFix: Path)</span> -&gt; bool:</span></span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<p>As description in the docstring, <code>fixupAndBundleLibsRecursively</code> can remove absolute references and budle all depedencies for all dylibs.</p>
<p>It traverses the directory, and for each file which is not symbol link, checks whether it ends with “.so” or “.dylib”, or there is “.so.” in the file name, or there is “.framework” in the full path and it’s a macOS binary. If it’s that case, call <code>bundleLibraryDependencies</code> method to bundle it in to <code>.app</code> package.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fixupAndBundleLibsRecursively</span><span class="params">(self, subdir: str)</span>:</span></span><br><span class="line">    <span class="string">"""Remove absolute references and budle all depedencies for all dylibs under :p subdir"""</span></span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line">    <span class="keyword">for</span> dirpath, dirs, files <span class="keyword">in</span> os.walk(os.path.join(self.appPath, subdir)):</span><br><span class="line">            <span class="keyword">for</span> filename <span class="keyword">in</span> files:</span><br><span class="line">                fullpath = Path(dirpath, filename)</span><br><span class="line">                <span class="keyword">if</span> fullpath.is_symlink():</span><br><span class="line">                    <span class="keyword">continue</span>  <span class="comment"># No need to update symlinks since we will process the target eventually.</span></span><br><span class="line">                <span class="keyword">if</span> (filename.endswith(<span class="string">".so"</span>)</span><br><span class="line">                        <span class="keyword">or</span> filename.endswith(<span class="string">".dylib"</span>)</span><br><span class="line">                        <span class="keyword">or</span> <span class="string">".so."</span> <span class="keyword">in</span> filename</span><br><span class="line">                        <span class="keyword">or</span> (<span class="string">f"<span class="subst">&#123;fullpath.name&#125;</span>.framework"</span> <span class="keyword">in</span> str(fullpath) <span class="keyword">and</span> utils.isBinary(str(fullpath)))):</span><br><span class="line">                    <span class="keyword">if</span> <span class="keyword">not</span> self.bundleLibraryDependencies(fullpath):</span><br><span class="line">                        CraftCore.log.info(<span class="string">"Failed to bundle dependencies for '%s'"</span>, os.path.join(dirpath, filename))</span><br><span class="line">                        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<p><code>areLibraryDepsOkay</code> can detect all the dependencies. If the library is not in <code>@rpath</code>, <code>@executable_path</code> or system library path, the dependencies cannot be satisfied on every mac. It may work relevant to the environment. But it will be a big problem.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">areLibraryDepsOkay</span><span class="params">(self, fullPath: Path)</span>:</span></span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line">    <span class="keyword">for</span> dep <span class="keyword">in</span> utils.getLibraryDeps(str(fullPath)):</span><br><span class="line">        <span class="keyword">if</span> dep == libraryId <span class="keyword">and</span> <span class="keyword">not</span> os.path.isabs(libraryId):</span><br><span class="line">            <span class="keyword">continue</span>  <span class="comment"># non-absolute library id is fine</span></span><br><span class="line">        <span class="comment"># @rpath and @executable_path is fine</span></span><br><span class="line">        <span class="keyword">if</span> dep.startswith(<span class="string">"@rpath"</span>) <span class="keyword">or</span> dep.startswith(<span class="string">"@executable_path"</span>):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="comment"># Also allow /System/Library/Frameworks/ and /usr/lib:</span></span><br><span class="line">        <span class="keyword">if</span> dep.startswith(<span class="string">"/usr/lib/"</span>) <span class="keyword">or</span> dep.startswith(<span class="string">"/System/Library/Frameworks/"</span>):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">if</span> dep.startswith(CraftStandardDirs.craftRoot()):</span><br><span class="line">            CraftCore.log.error(<span class="string">"ERROR: %s references absolute library path from craftroot: %s"</span>, relativePath,</span><br><span class="line">                                dep)</span><br><span class="line">        <span class="keyword">elif</span> dep.startswith(<span class="string">"/"</span>):</span><br><span class="line">            CraftCore.log.error(<span class="string">"ERROR: %s references absolute library path: %s"</span>, relativePath, dep)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            CraftCore.log.error(<span class="string">"ERROR: %s has bad dependency: %s"</span>, relativePath, dep)</span><br><span class="line">        found_bad_lib = <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<p>Here, in <code>checkLibraryDepsRecursively</code>, we traverse the directory to check all the dependencies of libraries, which is <code>.dylib</code> or <code>.so</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">checkLibraryDepsRecursively</span><span class="params">(self, subdir: str)</span>:</span></span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line">    <span class="keyword">for</span> dirpath, dirs, files <span class="keyword">in</span> os.walk(os.path.join(self.appPath, subdir)):</span><br><span class="line">        <span class="keyword">for</span> filename <span class="keyword">in</span> files:</span><br><span class="line">            fullpath = Path(dirpath, filename)</span><br><span class="line">            <span class="keyword">if</span> fullpath.is_symlink() <span class="keyword">and</span> <span class="keyword">not</span> fullpath.exists():</span><br><span class="line">                CraftCore.log.error(<span class="string">"Found broken symlink '%s' (%s)"</span>, fullpath,</span><br><span class="line">                                    os.readlink(str(fullpath)))</span><br><span class="line">                foundError = <span class="literal">True</span></span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> filename.endswith(<span class="string">".so"</span>) <span class="keyword">or</span> filename.endswith(<span class="string">".dylib"</span>) <span class="keyword">or</span> <span class="string">".so."</span> <span class="keyword">in</span> filename:</span><br><span class="line">                <span class="keyword">if</span> <span class="keyword">not</span> self.areLibraryDepsOkay(fullpath):</span><br><span class="line">                    CraftCore.log.error(<span class="string">"Found library dependency error in '%s'"</span>, fullpath)</span><br><span class="line">                    foundError = <span class="literal">True</span></span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<h2 id="Static-methods-in-class"><a href="#Static-methods-in-class" class="headerlink" title="Static methods in class"></a>Static methods in class</h2><p>The <code>_updateLibraryReference</code> method can use <code>install_name_tool -change</code> command to change a reference of dynamic library in a macOS/BSD binary.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@staticmethod</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_updateLibraryReference</span><span class="params">(fileToFix: Path, oldRef: str, newRef: str = None)</span> -&gt; bool:</span></span><br><span class="line">    <span class="keyword">if</span> newRef <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        basename = os.path.basename(oldRef)</span><br><span class="line">        newRef = <span class="string">"@executable_path/../Frameworks/"</span> + basename</span><br><span class="line">    <span class="keyword">with</span> utils.makeWritable(fileToFix):</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> utils.system([<span class="string">"install_name_tool"</span>, <span class="string">"-change"</span>, oldRef, newRef, str(fileToFix)], logCommand=<span class="literal">False</span>):</span><br><span class="line">            CraftCore.log.error(<span class="string">"%s: failed to update library dependency path from '%s' to '%s'"</span>,</span><br><span class="line">                                fileToFix, oldRef, newRef)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<p>The <code>_getLibraryNameId</code> method can use <code>otool -D</code> to get the identity of a dynamic library in a macOS/BSD binary.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@staticmethod</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_getLibraryNameId</span><span class="params">(fileToFix: Path)</span> -&gt; str:</span></span><br><span class="line">    libraryIdOutput = io.StringIO(</span><br><span class="line">        subprocess.check_output([<span class="string">"otool"</span>, <span class="string">"-D"</span>, str(fileToFix)]).decode(<span class="string">"utf-8"</span>).strip())</span><br><span class="line">    lines = libraryIdOutput.readlines()</span><br><span class="line">    <span class="keyword">if</span> len(lines) == <span class="number">1</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">""</span></span><br><span class="line">    <span class="comment"># Should have exactly one line with the id now</span></span><br><span class="line">    <span class="keyword">assert</span> len(lines) == <span class="number">2</span>, lines</span><br><span class="line">    <span class="keyword">return</span> lines[<span class="number">1</span>].strip()</span><br></pre></td></tr></table></figure>
<p>The <code>_fixupLibraryId</code> method can use <code>install_name_tool -id</code> to try to fix the absolute identity of a dynamic library in a macOS/BSD binary.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@classmethod</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_fixupLibraryId</span><span class="params">(cls, fileToFix: Path)</span>:</span></span><br><span class="line">    libraryId = cls._getLibraryNameId(fileToFix)</span><br><span class="line">    <span class="keyword">if</span> libraryId <span class="keyword">and</span> os.path.isabs(libraryId):</span><br><span class="line">        CraftCore.log.debug(<span class="string">"Fixing library id name for %s"</span>, libraryId)</span><br><span class="line">        <span class="keyword">with</span> utils.makeWritable(fileToFix):</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> utils.system([<span class="string">"install_name_tool"</span>, <span class="string">"-id"</span>, os.path.basename(libraryId), str(fileToFix)],</span><br><span class="line">                                logCommand=<span class="literal">False</span>):</span><br><span class="line">                CraftCore.log.error(<span class="string">"%s: failed to fix absolute library id name for"</span>, fileToFix)</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This class is a magic class which can achieve almost everything on macOS.</p>
<p>But the code style is a little confusing. And the parameters are not agreed. Some methods use <code>str</code> to represent a path, some use <code>Path</code>.</p>
<p>Maybe this can be also improved in the future.</p>
<p>Anyway, it’s really a helpful class.</p>
<h1 id="Improvement"><a href="#Improvement" class="headerlink" title="Improvement"></a>Improvement</h1><p>During my bonding period, I found that there is a library named <code>qca-qt5</code> is not fixed appropriately. It caused a crash.</p>
<h2 id="Locating-the-problem"><a href="#Locating-the-problem" class="headerlink" title="Locating the problem"></a>Locating the problem</h2><p>After analyzing of crash log, I found that the library <code>qca-qt5</code> is loaded twice. Two libraries with same dynamic library id caused this crash.<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">qca-qt5 (0) &lt;14AD33D7-196F-32BB-91B6-598FA39EEF20&gt; /Volumes/*/kdeconnect-indicator.app/Contents/Frameworks/qca-qt5</span><br><span class="line">(??? - ???) &lt;14AD33D7-196F-32BB-91B6-598FA39EEF20&gt; /Users/USER/*/qca-qt5.framework/Versions/2.2.0/qca-qt5</span><br></pre></td></tr></table></figure></p>
<p>One is in the <code>.app</code> package, the other is in <code>CraftRoot/lib</code>.</p>
<img src="/2019/05/26/Craft-packager/Craft-lib.png" title="Craft/lib">
<img src="/2019/05/26/Craft-packager/Frameworks-lib.png" title="Frameworks">
<p>As far as I know, <code>qca-qt5</code> tried to search its plugins in some path. The one in the package is not fixed, so it started a searching of plugins in the <code>CraftRoot/lib</code> directory. The plugins in it refer the <code>qca-qt5</code> in the directory. So the two libraries with the same name are loaded, and the application crashed.</p>
<h2 id="Cause"><a href="#Cause" class="headerlink" title="Cause"></a>Cause</h2><p>With good knowing of <code>MacDylibBundler</code>, we can improve it to fix the bug. And this will be helpful to other applications or libraries in <code>Craft</code>.</p>
<p>I noticed that all the libraries with <code>.dylib</code> can be handled correctly. The problem is based on the libraries in the <code>.framework</code> package. It seems that <code>Craft</code> cannot handle the dynamic libraries in the <code>.framework</code> correctly.</p>
<p>And we can see that, in <code>checkLibraryDepsRecursively</code>, only <code>.so</code> and <code>.dylib</code> are checked. So this is a bug covered deeply.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">CRAFT: ➜  MacOS otool -L kdeconnectd</span><br><span class="line">kdeconnectd:</span><br><span class="line">	/Volumes/Storage/Inoki/CraftRoot/lib/libkdeconnectcore.1.dylib (compatibility version 1.0.0, current version 1.3.3)</span><br><span class="line">	/Volumes/Storage/Inoki/CraftRoot/lib/libKF5KIOWidgets.5.dylib (compatibility version 5.0.0, current version 5.57.0)</span><br><span class="line">	/Volumes/Storage/Inoki/CraftRoot/lib/libKF5Notifications.5.dylib (compatibility version 5.0.0, current version 5.57.0)</span><br><span class="line">	/Volumes/Storage/Inoki/CraftRoot/lib/qca-qt5.framework Versions/2.2.0/qca-qt5 (compatibility version 2.0.0, current version 2.2.0)</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure>
<p>In the <code>_addLibToAppImage</code> method, the library in the <code>framework</code> is copied directly to the <code>Contents/Frameworks</code>. For example, <code>lib/qca-qt5.framework/Versions/2.2.0/qca-qt5</code> becomes <code>Contents/Frameworks/qca-qt5</code>.</p>
<p>And then, during the fix in <code>fixupAndBundleLibsRecursively</code> method, according to the following code, it will not be fixed. Although it should be in a <code>.framework</code> directory and it’s a binary, after <code>_addLibToAppImage</code>, it will not be in a <code>.framework</code> directory. So it will not be fixed.<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (filename.endswith(<span class="string">".so"</span>)</span><br><span class="line">        <span class="keyword">or</span> filename.endswith(<span class="string">".dylib"</span>)</span><br><span class="line">        <span class="keyword">or</span> <span class="string">".so."</span> <span class="keyword">in</span> filename</span><br><span class="line">        <span class="keyword">or</span> (<span class="string">f"<span class="subst">&#123;fullpath.name&#125;</span>.framework"</span> <span class="keyword">in</span> str(fullpath) <span class="keyword">and</span> utils.isBinary(str(fullpath)))):</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> self.bundleLibraryDependencies(fullpath):</span><br><span class="line">        CraftCore.log.info(<span class="string">"Failed to bundle dependencies for '%s'"</span>, os.path.join(dirpath, filename))</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure></p>
<h2 id="Fixing-it"><a href="#Fixing-it" class="headerlink" title="Fixing it !"></a>Fixing it !</h2><p>To fix it, I think a good idea is copying all the <code>.framework</code> directory and keeping its structure.</p>
<p>I firstly do a checking in the <code>_addLibToAppImage</code> method. For example, if <code>qca-qt5</code> is in the <code>qca-qt5.framework</code> subdirectory, we change the <code>libBasename</code> to <code>qca-qt5.framework/Versions/2.2.0/qca-qt5</code>. So the <code>targetPath</code> can also be updated correctly.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">libBasename = libPath.name</span><br><span class="line"></span><br><span class="line"><span class="comment"># Handle dylib in framework</span></span><br><span class="line"><span class="keyword">if</span> <span class="string">f"<span class="subst">&#123;libPath.name&#125;</span>.framework"</span> <span class="keyword">in</span> str(libPath):</span><br><span class="line">    libBasename = str(libPath)[str(libPath).find(<span class="string">f"<span class="subst">&#123;libPath.name&#125;</span>.framework"</span>):]</span><br><span class="line"></span><br><span class="line">targetPath = Path(self.appPath, <span class="string">"Contents/Frameworks/"</span>, libBasename)</span><br><span class="line"><span class="keyword">if</span> targetPath.exists() <span class="keyword">and</span> targetPath <span class="keyword">in</span> self.checkedLibs:</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<p>After several checkings, an important section is copying the library. I add some code to check if the library is in a <code>.framework</code> directory. If a library is in a <code>.framework</code> directory, I try to copy the entire directory to the <code>Contents/Frameworks</code>. So for <code>qca-qt5</code>, it should be <code>Contents/Frameworks/qca-qt5.framework/Versions/2.2.0/qca-qt5</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> targetPath.exists():</span><br><span class="line">    <span class="keyword">if</span> <span class="string">f"<span class="subst">&#123;libPath.name&#125;</span>.framework"</span> <span class="keyword">in</span> str(libPath):</span><br><span class="line">        <span class="comment"># Copy the framework of dylib</span></span><br><span class="line">        frameworkPath = str(libPath)[:(str(libPath).find(<span class="string">".framework"</span>) + len(<span class="string">".framework"</span>))]</span><br><span class="line">        frameworkTargetPath = str(targetPath)[:(str(targetPath).find(<span class="string">".framework"</span>) + len(<span class="string">".framework"</span>))]</span><br><span class="line">        utils.copyDir(frameworkPath, frameworkTargetPath, linkOnly=<span class="literal">False</span>)</span><br><span class="line">        CraftCore.log.info(<span class="string">"Added library dependency '%s' to bundle -&gt; %s"</span>, frameworkPath, frameworkTargetPath)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        utils.copyFile(str(libPath), str(targetPath), linkOnly=<span class="literal">False</span>)</span><br><span class="line">        CraftCore.log.info(<span class="string">"Added library dependency '%s' to bundle -&gt; %s"</span>, libPath, targetPath)</span><br></pre></td></tr></table></figure>
<p>After copying, another important point is in <code>_updateLibraryReference</code>. If a library is in a <code>.framework</code> directory, the new reference should be <code>@executable_path/../Frameworks/*.framework/...</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> newRef <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">    basename = os.path.basename(oldRef)</span><br><span class="line">    <span class="keyword">if</span> <span class="string">f"<span class="subst">&#123;basename&#125;</span>.framework"</span> <span class="keyword">in</span> oldRef:</span><br><span class="line">        <span class="comment"># Update dylib in framework</span></span><br><span class="line">        newRef = <span class="string">"@executable_path/../Frameworks/"</span> + oldRef[oldRef.find(<span class="string">f"<span class="subst">&#123;basename&#125;</span>.framework"</span>):]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        newRef = <span class="string">"@executable_path/../Frameworks/"</span> + basename</span><br></pre></td></tr></table></figure>
<p>After fixing, the executable can be launched without crash.</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">CRAFT: ➜  MacOS otool -L kdeconnectd</span><br><span class="line">kdeconnectd:</span><br><span class="line">	@executable_path/../Frameworks/libkdeconnectcore.1.dylib (compatibility version 1.0.0, current version 1.3.3)</span><br><span class="line">	@executable_path/../Frameworks/libKF5KIOWidgets.5.dylib (compatibility version 5.0.0, current version 5.57.0)</span><br><span class="line">	@executable_path/../Frameworks/libKF5Notifications.5.dylib (compatibility version 5.0.0, current version 5.57.0)</span><br><span class="line">	@executable_path/../Frameworks/qca-qt5.framework/Versions/2.2.0/qca-qt5 (compatibility version 2.0.0, current version 2.2.0)</span><br><span class="line">    ...</span><br><span class="line">CRAFT: ➜  MacOS ./kdeconnectd</span><br><span class="line">kdeconnect.core: KdeConnect daemon starting</span><br><span class="line">kdeconnect.core: onStart</span><br><span class="line">kdeconnect.core: KdeConnect daemon started</span><br><span class="line">kdeconnect.core: Broadcasting identity packet</span><br></pre></td></tr></table></figure>
<h1 id="Conclusion-1"><a href="#Conclusion-1" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>In the software development, there are always some cases which we cannot consider. Open Source gives us the possibility of collecting intelligence from people all over the world to handle such cases.</p>
<p>That’s also why I like Open Source so much.</p>
<p>Today is the first day of coding period, I hope all goes well for the community and all GSoC students :)</p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-05-26T21:57:22.000Z">2019-05-26</time>
	</a>
</span>

	<span class="cat-links font-asesome-icon">
		
				<a href="/categories/Craft/" rel="category tag">Craft</a>
		
				<a href="/categories/Craft/Package/" rel="category tag">Package</a>
		
	</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/Craft/" rel="category tag">Craft</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/05/26/Craft-packager/" class="leancloud_visitors" data-flag-title="KDE Craft Packager on macOS">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/05/19/Craft-vlc/" rel="bookmark">KDE Craft now delivers with vlc and libvlc on macOS</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<p>Lacking <code>VLC</code> and <code>libvlc</code> in <code>Craft</code>, <code>phonon-vlc</code> cannot be built successfully on macOS. It caused the failed building of <code>KDE Connect</code> in <code>Craft</code>.</p>
<p>As a small step of my <code>GSoC</code> project, I managed to build <code>KDE Connect</code> by removing the <code>phonon-vlc</code> dependency. But it’s not a good solution. I should try to fix <code>phonon-vlc</code> building on macOS. So during the community bonding period, to know better the community and some important tools in the Community, I tried to fix <code>phonon-vlc</code>.</p>
<h1 id="Fixing-phonon-vlc"><a href="#Fixing-phonon-vlc" class="headerlink" title="Fixing phonon-vlc"></a>Fixing phonon-vlc</h1><p>At first, I installed <code>libVLC</code> in <code>MacPorts</code>. All Header files and libraries are installed into the system path. So theoretically, there should not be a problem of the building of <code>phonon-vlc</code>. But an error occurred:</p>
<img src="/2019/05/19/Craft-vlc/phonon-vlc-link-error.png" title="Phonon-vlc link error">
<p>We can figure that the compiling is ok, the error is just at the end, during the linking. The error message tells us there is no <code>QtDBus</code> lib. So to fix it, I made a small patch to add QtDBus manually in the CMakeLists file.</p>
<figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt</span><br><span class="line">index 47427b2..1cdb250 100644</span><br><span class="line"><span class="comment">--- a/src/CMakeLists.txt</span></span><br><span class="line"><span class="comment">+++ b/src/CMakeLists.txt</span></span><br><span class="line">@@ -81,7 +81,7 @@ if(APPLE)</span><br><span class="line"> endif(APPLE)</span><br><span class="line"> </span><br><span class="line"> automoc4_add_library(phonon_vlc MODULE $&#123;phonon_vlc_SRCS&#125;)</span><br><span class="line"><span class="deletion">-qt5_use_modules(phonon_vlc Core Widgets)</span></span><br><span class="line"><span class="addition">+qt5_use_modules(phonon_vlc Core Widgets DBus)</span></span><br><span class="line"> </span><br><span class="line"> set_target_properties(phonon_vlc PROPERTIES</span><br><span class="line">     PREFIX ""</span><br></pre></td></tr></table></figure>
<p>And it works well!</p>
<p>A small problem is that Hannah said she didn’t get an error during linking. It may be something about Qt version. If someone gets some idea, welcome to <a href="mailto:veyx.shaw@gmail.com" target="_blank" rel="noopener">contact me</a>.</p>
<p>My Qt version is <code>5.12.3</code>.</p>
<h1 id="Fixing-VLC"><a href="#Fixing-VLC" class="headerlink" title="Fixing VLC"></a>Fixing VLC</h1><p>To fix <code>VLC</code>, I tried to pack the <code>VLC</code> binary just like the one on <code>Windows</code>.</p>
<p>But unfortunately, in the <code>.app</code> package, the Header files are not completed. Comparing to Windows version, the entire <code>plugins</code> folder is missing.</p>
<img src="/2019/05/19/Craft-vlc/vlc-windows-include.jpg" title="Windows">
<img src="/2019/05/19/Craft-vlc/vlc-mac-include.png" title="macOS">
<p>So I made a patch for all those files. But the patch is too huge (25000 lines!). So it is not a good idea to merge it into master branch.</p>
<p>Thanks to Hannah, she has made a <code>libs/vlc</code> blueprint in the master branch, so in <code>Craft</code>, feel free to install it by running <code>craft libs/vlc</code>.</p>
<h1 id="Troubleshooting"><a href="#Troubleshooting" class="headerlink" title="Troubleshooting"></a>Troubleshooting</h1><p>If you cannot build <code>libs/vlc</code>, just like me, you can also choose the binary version <code>VLC</code> with Header files patch.</p>
<p>The patch of Headers for binary is too big. Adding it to the master branch is not a good idea. So I published it on my own repository:<br><a href="https://github.com/Inokinoki/craft-blueprints-inoki" target="_blank" rel="noopener">https://github.com/Inokinoki/craft-blueprints-inoki</a></p>
<p>To use it, run <code>craft --add-blueprint-repository https://github.com/inokinoki/craft-blueprints-inoki.git</code> and the blueprint(s) will be added into your local blueprint directory.</p>
<p>Then, <code>craft binary/vlc</code> will help get the vlc binary and install Header files, libraries into <code>Craft</code> include path and lib path. Finally, you can build what you want with <code>libvlc</code> dependency.</p>
<h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>Up to now, <code>KDE Connect</code> is using <code>QtMultimedia</code> rather than <code>phonon</code> and <code>phonon-vlc</code> to play a sound. But this work could be also useful for other applications or libraries who depend on <code>phonon</code>, <code>phonon-vlc</code> or <code>vlc</code>. This small step may help build them successfully on macOS.</p>
<p>I hope this can help someone!</p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-05-19T19:57:22.000Z">2019-05-19</time>
	</a>
</span>

	<span class="cat-links font-asesome-icon">
		
				<a href="/categories/Craft/" rel="category tag">Craft</a>
		
				<a href="/categories/Craft/Blueprints/" rel="category tag">Blueprints</a>
		
	</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/Craft/" rel="category tag">Craft</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/05/19/Craft-vlc/" class="leancloud_visitors" data-flag-title="KDE Craft now delivers with vlc and libvlc on macOS">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  
    
<article class="hentry ">
		
		
			<header class="entry-header">
				<h2 class="entry-title"><a class="post-title-link" href="/2019/05/09/Inoki/" rel="bookmark">About me</a></h2>	
			</header>
		
		<!-- .entry-header -->
		<div class="entry-content">
			
				<p>Hi, everyone!</p>
<p>I’m Weixuan XIAO, with the nickname: <strong>Inoki</strong>, sometimes <strong>Inokinoki</strong> is used to avoid duplicated username.</p>
<p>I’m glad to be selected in Google Summer of Code 2019 to work for KDE Community to make KDE Connect work on macOS. And I’m willing to be a long-term contributor in KDE Community.</p>
<p>As a Chinese student, I’m studying in France for my engineering degree. At the same time, I’m waiting for my bachelor degree at Shanghai University.</p>
<p>I major in Real-Time System and Embedded Engineering. With strong interests in Operating System and Computer Architecture, I like playing with small devices like Arduino and Raspberry Pi, different systems like macOS and Linux(especially Manjaro with KDE, they are the best partner).</p>
<p>Japanese culture makes me crazy, for example, the animation and the game. Even my nickname is actually the pronunciation of my real name in Japanese. So if all of these is the choice of <strong>Steins Gate</strong>, I’ll normally accept them :)</p>
<p>I speak Chinese, French, English, and a little Japanese. But I realize that my English is awful. So if I make any mistake, please tell me. This would improve my English and I will appreciate it.</p>
<p>I hope we can have a good summer in 2019. And have some good codes :)</p>

			
		</div><!-- .entry-content -->
		
		
		<footer class="entry-footer">
			<span class="posted-on font-asesome-icon">
	<a href="" rel="bookmark">
	<time class="updated" datetime="2019-05-09T22:11:22.000Z">2019-05-09</time>
	</a>
</span>


	<span class="tags-links font-asesome-icon">
	
		<a href="/tags/Inoki/" rel="category tag">Inoki</a>
    
</span>
		

    <span class="eye font-asesome-icon" >
         <span id="/2019/05/09/Inoki/" class="leancloud_visitors" data-flag-title="About me">
        
        </span>
    </span>

			<br/>
			<a href="https://www.vultr.com/?ref=7746405"><img src="https://www.vultr.com/media/banner_1.png" width="728" height="90"></a>
		</footer><!-- .entry-footer -->
</article>
<div class="misc">
    <a href="#main"><span class="top font-asesome-icon">Top</span></a>
</div>
  



			</main>
		</div>
		<footer id="colophon" class="site-footer">
			<div class="site-info">
				<span>powered by <a href="https://hexo.io/">Hexo</a>&nbsp;&nbsp; theme by <a href="https://github.com/Troy-Yang/hexo-theme-twentyfifteen-wordpress">Troy</a> &nbsp; inspired by <a href="https://wordpress.org/themes/">wordpress</a></span>
			</div><!-- .site-info -->
		</footer>
	</div>
	<!-- <div id="infinite-footer">
            <div class="container">
                <div class="blog-info">
                    <a id="infinity-blog-title" href="#" rel="home" title="Scroll back to top">
                         Happy Coding, Happy Life!
                    </a>
                </div>
                <div class="blog-credits">
					<span>蜀ICP备15004900号-1</span>
                </div>
            </div>
        </div>#infinite-footer -->
    <!-- After footer scripts -->
    <script src="/js/jquery-3.1.1.min.js"></script>
<script src="/js/main.js"></script>

    <!--referring from https://notes.wanghao.work/2015-10-21-%E4%B8%BANexT%E4%B8%BB%E9%A2%98%E6%B7%BB%E5%8A%A0%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1%E5%8A%9F%E8%83%BD.html -->
    <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.1.js"></script>
    <script>
        AV.initialize("QC5aQ8pUah279L1skNerLRTU-gzGzoHsz", "mhvEFRRIuPHtjNgOXAe9axsk");
    </script>
    <script>
        function showTime(Counter) {
            var query = new AV.Query(Counter);
            $(".leancloud_visitors").each(function () {
                var url = $(this).attr("id").trim();
                query.equalTo("url", url);
                query.find({
                    success: function (results) {
                        if (results.length == 0) {
                            var content = $(document.getElementById(url)).text() + ': 0';
                            $(document.getElementById(url)).text(content);
                            return;
                        }
                        for (var i = 0; i < results.length; i++) {
                            var object = results[i];
                            //var content = $(document.getElementById(url)).text() + ': ' + object.get('time');
                            $(document.getElementById(url)).text(object.get('time'));
                        }
                    },
                    error: function (object, error) {
                        console.log("Error: " + error.code + " " + error.message);
                    }
                });
            });
        }
        function addCount(Counter) {
            var Counter = AV.Object.extend("Counter");
            url = $(".leancloud_visitors").attr('id').trim();
            title = $(".leancloud_visitors").attr('data-flag-title').trim();
            var query = new AV.Query(Counter);
            query.equalTo("url", url);
            query.find({
                success: function (results) {
                    if (results.length > 0) {
                        var counter = results[0];
                        counter.fetchWhenSave(true);
                        counter.increment("time");
                        counter.save(null, {
                            success: function (counter) {
                                //var content = $(document.getElementById(url)).text() + ': ' + counter.get('time');
                                // remove ': '
                                $(document.getElementById(url)).text(counter.get('time'));
                            },
                            error: function (counter, error) {
                                console.log('Failed to save Visitor num, with error message: ' + error.message);
                            }
                        });
                    } else {
                        var newcounter = new Counter();
                        newcounter.set("title", title);
                        newcounter.set("url", url);
                        newcounter.set("time", 1);
                        newcounter.save(null, {
                            success: function (newcounter) {
                                console.log("newcounter.get('time')=" + newcounter.get('time'));
                                var content = $(document.getElementById(url)).text() + ': ' + newcounter.get('time');
                                // remove ': '
                                $(document.getElementById(url)).text(newcounter.get('time'));
                            },
                            error: function (newcounter, error) {
                                console.log('Failed to create');
                            }
                        });
                    }
                },
                error: function (error) {
                    console.log('Error:' + error.code + " " + error.message);
                }
            });
        }
        $(function () {
            var Counter = AV.Object.extend("Counter");
            if ($('.leancloud_visitors').length == 1) {
                addCount(Counter);
            } else if ($('.post-title-link').length > 1) {
                showTime(Counter);
            }
        });
    </script>
    

</body>
</html>